#!/usr/bin/env python
#
# Copyright 2022 Huaqin Technology. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#    * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#    * Neither the name of Huaqin Technology nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Does tws codestyle check on c files.

The goal of this script is to identify places in the code that "may" be in
non-compliance with BD1 coding style. It does not attempt to fix up these
problems -- the point is to educate. It does also not attempt to find all
problems, or to ensure that everything it does find is legitimately a
problem.
"""

import sys
import re

# & 'C:\Program Files\Python37\python.exe' -m pydoc -w .\tws_c_codestyle_v0.3.py
_USAGE = """
Synatax: xxx.py <file> [files]
"""

# The test files for this script.
_test_files = None
# _test_files = ['./test_code/ux_manager.c', './test_code/ux_manager.h']

# The valid extentions for filename
_valid_extentions = ['c', 'h']
_valid_extentions_re_pattern = r'[ch]'

# lines加不加前后空行, 对每次计算rang有影响, 使用宏定义消除影响
_LINE_LENS_RANGE_START = 0 # 给range函数使用, 起始行
_LINE_LENS_RANGE_STOP = 0 # 给range函数结束使用, 结束行
_LINE_LENS_TRUE = 0 # lines的真实行数
_LINE_LENS_WITH_MARKER = 0 # lines加上了前后MARKER的行数

# 每一行的最大长度
_MAX_LINE_LENS = 80

class _TWSLintState(object):
  """Maintains code checker states"""
  def __init__(self) -> None:
    self.error_count = 0
  
  def ResetErrorCounts(self):
    self.error_count = 0


_clint_state = _TWSLintState()


def _PrintUsage(message):
  """Print a brief usage string and exits, optionally with an error message.

  Args:
    message: The optional error message.
  """
  print(_USAGE)
  print('The valid file extentions for file name is:')
  print(_valid_extentions)

  if message:
    sys.exit('\nFatal _Error: ' + message)
  else:
    sys.exit(1)



def _Error(filename, linenum, category, confidence, message):
  """Logs the error we have found

  Args:
    filename: The name of the file containing the error.
    linenum: The number of the line containing the error.
    category: A string used to describe the error.
    confidence: A number from 1-5 representing a confidence score
      for the error. 
      5: We are certain of the problem.
      1: It could be a legitimate construct.
  """
  print('%s:%s:  [%s]  [%d]  %s' % (
          filename, linenum, category, confidence, message))


_MIN_LINE_COUNT = 3 # 定义
def _CheckFileNotEmpty(lines):
  """根据文件中行的数量, 确定文件是否为空.
  文件最小行数由"_MIN_LINE_COUNT"定义, 当小于这个行数时, 判定文件为空.
  
  Args: 
    lines: 文件中所有的行列表.
  """
  
  if lines.count <= _MIN_LINE_COUNT:
    return False
  else:
    return True


def _GetFileNameWithoutPath(filename):
  """去掉文件名中的路径, 并返回完整的文件名.

  Args:
    filename: 需要处理的文件名(包含完整路径)

  Returns:
    不带路径的文件名.
  """
  name = re.sub(r'\\', '/', filename) # 将反斜杠'\' 先替换为'/', 便于split处理.
  name = re.split(r'/', name)[-1] # 使用'/'分割字符串, 并取出最后一个元素（文件名）.
  if not name:
    _PrintUsage('filename error' + filename)

  return name


def _CountLeftSpace(line):
  """计算字符串左侧的空格数量, 并返回空格数量
  只计算左侧字符串, 用于比较两行的缩进是否符合规范.

  Args:
    line: 需要计算左侧空格数量的字符串.

  Returns: 
    字符串左侧的空格数量.
  """
  return len(line) - len(line.lstrip())


def GuardCheckForFilenameFormat(filename, file_extention, lines):
  """检查文件命名规范.
  1. 名字全部使用英文小写字母a-z, 中间可以有下划线'_'.
  2. 除了文件类型后缀'.c''.h', 文件名中不能有其他的'.'符号.
  3. 不能使用'.'或'_开头', 只能使用英文字母开头.

  Errors: 
    category: [whitespace/filename]
    confidence: [5]
    message: [Filename format error: "xxx_xxx.h"]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  _FILE_NAME_FORMAT_PATTERN = re.compile(r'^[a-z][a-z_]*.' + _valid_extentions_re_pattern + r'$')
  if not _FILE_NAME_FORMAT_PATTERN.match(_GetFileNameWithoutPath(filename)):
    _Error(filename, 0, 'whitespace/filename', 5,
      'Filename format error: "xxx_xxx.h"')


def GuardCheckForCopyright(filename, file_extention, lines):
  """检查文件是否有copyright.
  查找文件的第0~11行, 检查其中是否有"Copyright"字符串

  Errors: 
    category: [legal/copyright]
    confidence: [5]
    message: [File need Copyright: "Copyright [year] <Copyright Owner>"]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """

  for line_num in range(_LINE_LENS_RANGE_START, min(_LINE_LENS_TRUE, 11)):
    if re.search(r'Copyright', lines[line_num], re.I):
      break

  _Error(filename, 0, 'legal/copyright', 5,
        'No copyright message found.  '
        'Need Copyright: "Copyright [year] <Copyright Owner>"')


def GuardCheckForMarcoFormat(filename, file_extention, lines):
  """检查宏书写规范.
  1. 每行顶格书写: '#if' 前不能有空格.
  2. 需要顶格书写的有: '#if', '#endif', '#endif', '#define'
  TODO: 3. 检查宏命名规范: 全部只包含大写英文字母[A-Z]和下划线[_]

  Errors: 
    category: [whitespace/marco]
    confidence: [5]
    message: [Marco format error: no indentation before "#" symbol."]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  rule = re.compile(r'\s+(#if|#endif|#elif|#define)')
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if rule.match(lines[line_num]):
      _Error(filename, line_num, 'whitespace/marco', 5,
        'Marco format error: no indentation before "#" symbol.')


def GuardCheckForNewlineAtEndOfFile(filename, file_extention, lines):
  """检查文件最后一行是否为空行.
  1. 文件最后一行, 应为空行.
    Errors: 
      category: [whitespace/ending_newline]
      confidence: [5]
      message: [Need new-line at the end of file]
  2. 文件倒数第二行，应有代码。
    Errors: 
      category: [whitespace/ending_newline]
      confidence: [5]
      message: [Only 1 blank line at the end of the file.]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  if lines[-2].strip():
    _Error(filename, _LINE_LENS_TRUE, 'whitespace/ending_newline', 5,
      'Need new-line at the end of file')
  elif not lines[-3].strip():
    _Error(filename, _LINE_LENS_TRUE, 'whitespace/ending_newline', 5,
      'Only 1 blank line at the end of the file.')


def GuardCheckForLineLength(filename, file_extention, lines):
  """检查每行字符长度
  1. 包含前后空白字符.
  2. 最大字符数量由"_MAX_LINE_LENS"定义.

  Errors: 
    category: [whitespace/line_length]
    confidence: [5]
    message: [Lines should be <= %i characters long, but you have %i]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if len(lines[line_num]) > _MAX_LINE_LENS:
      _Error(filename, line_num, 'whitespace/line_length', 5,
        'Lines should be <= %i characters long, but you have %i' % (_MAX_LINE_LENS, len(lines[line_num])))


def GuardCheckForLineEndingWithBlank(filename, file_extention, lines):
  """检查行尾无用空白字符
  1. 检查行尾是否有空白(空格、TAB、软换行等)
  TODO: 2. 将行尾的空白字符去掉drop, 便于后续规范检查, 减少重复报错.

  Errors: 
    category: [whitespace/unused_space]
    confidence: [5]
    message: [Please remove unused space or tab at end of the line]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  _LINE_END_WITH_SPACE_PATTERN = re.compile(r'^.*\s+$')
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _LINE_END_WITH_SPACE_PATTERN.match(lines[line_num]):
      _Error(filename, line_num, 'whitespace/unused_space', 5,
        'Please remove unused space or tab at end of the line')


def GuardCheckForTabInLine(filename, file_extention, lines):
  """检查否有非法符号
  1. 文件中不能包含'\\t', '\\f', '\\r'等字符.

  Errors: 
    category: [whitespace/tab]
    confidence: [5]
    message: [Please remove illegal symbols: "\\t", "\\f", "\\r".]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  _TAB_symbol_IN_LINE_PATTERN = re.compile(r'\r|\t|\f')
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _TAB_symbol_IN_LINE_PATTERN.search(lines[line_num]):
      _Error(filename, line_num, 'whitespace/tab', 5,
        'Please remove illegal symbols: "\\t", "\\f", "\\r".')


def GuardCheckForLanguage(filename, file_extention, lines):
  """检查是否有中文字符
  1. 文件中不能包含中文字符.

  Errors: 
    category: [whitespace/language]
    confidence: [5]
    message: [Cannot contain Chinese characters.]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  _LANGUAGE_CHINESE_PATTERN = re.compile(r'[\u4e00-\u9fa5]')
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
      if _LANGUAGE_CHINESE_PATTERN.search(lines[line_num]):
        _Error(filename, line_num, 'whitespace/language', 5,
          'Cannot contain Chinese characters.')


def GuardCheckForIncludeFormat(filename, file_extention, lines):
  """检查'#include'格式.
  1. '#include'顶行写, 前面不能有空格.
  2. '#include'后只有一个空格, 不能有多个空格
  3. 需要使用双引号, 不使用尖括号: 正确: '#include "xxx.h"',  错误: '#include <xxx.h>'
  4. 不能带路径.错误: '#include "../aaa/bbb/xxx.h"'
  TODO: 同一个客户的头文件放在一起.比如Green客户的头文件, 都带'green_'前缀, 需要放到一起.

  Errors: 
    category: [whitespace/include]
    confidence: [5]
    message: ["#include" format error.]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  _INCLUDE_PATTERN = re.compile(r'^\s*#include')
  _INCLUDE_FORMAT_PATTERN = re.compile(r'^#include "[^..].*\.h"$')
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _INCLUDE_PATTERN.match(lines[line_num]) and not _INCLUDE_FORMAT_PATTERN.match(lines[line_num]):
      _Error(filename, line_num, 'whitespace/include', 5,
        '"#include" format error.')


def GuardCheckForIncludeInHeaderFile(filename, file_extention, lines):
  """检查头文件的文件引用.
  1. 头文件中, 不应该包含其他头文件(不能有'#include'符号).

  Errors: 
    category: [whitespace/header_file]
    confidence: [5]
    message: [Header file(xxx.h) cannot include headerfiles.]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  if file_extention != 'h':
    return
  _INCLUDE_PATTERN = re.compile(r'^\s*#include')
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _INCLUDE_PATTERN.match(lines[line_num]):
      _Error(filename, line_num, 'whitespace/header_file', 5,
        'Header file(xxx.h) cannot include headerfiles.')


def GuardCheckForHeaderFileHaveMarcoProtect(filename, file_extention, lines):
  """检查头文件的宏保护.
  1. 使用与文件名同名的宏, 如头文件aaa_bbb.h, 应使用__AAA_BBB_H__来保护.
  2. 前后需使用双下划线"__"
  example:
    filename: aaa_bbb.h
    #ifndef __XXX_XXX_H__ // 必须在第一行.
    #define __XXX_XXX_H__ // 必须在第二行.
    ///code
    #endif // __XXX_XXX_H__ // 必须在倒数第一行.

  Errors: 
    category: [whitespace/header_file]
    confidence: [5]
    message: [Header file(xxx.h) need to be protected correctly.]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  if file_extention != 'h':
    return
  marco = "__" + re.sub(r'\.', '_', _GetFileNameWithoutPath(filename).upper()) + "__"
  if (lines[1] != '#ifndef ' + marco) and (lines[2] != '#define ' + marco) and (lines[-3] != '#endif // ' + marco):
    _Error(filename, 0, 'whitespace/header_file', 5,
        'Header file(xxx.h) need to be protected correctly.')

def GuardCheckForSrcFileUseExtern(filename, file_extention, lines):
  """禁止在'.c'文件中使用'extern'.
  1. '.c'文件中, 不能有extern开头的行.

  Errors: 
    category: [whitespace/c_file]
    confidence: [5]
    message: [Src file(xxx.c) forbid to use "extern".]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  if file_extention != 'c':
    return

  _EXTERN_PATTERN = re.compile(r'^\s*extern')
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _EXTERN_PATTERN.match(lines[line_num]):
      _Error(filename, 0, 'whitespace/c_file', 5,
        'Src file(xxx.c) forbid to use "extern".')


def GuardCheckForSingleLineCommentAndReplaceByBlank(filename, file_extention, lines):
  """检查单行注释('//')规范
  单行注释, 分为新行模式 和 代码行模式: 
  1. 新行模式: 整个注释位于一个独立的新行, 前面没有代码.
  2. 代码行模式: 注释位于代码最后.

  3. 公共规则: 
    a. "//"符号与注释间, 应有且只有一个空格: "// comments"
    b. 禁止注释内容为空的无效注释: 
      "//////  "
      "//   "
    c. 不能同时有多于两个的斜杠:'///'
  4. 新行模式规则: 
    a. 当"//"位于新行时, 应位于代码上方一行, 无空行:
      "// comments"
      "code;"
    b. 当"//"位于新行时, 缩进应保持与代码一致(在缩进中检查)
      "    // comments"
      "    code;"
  5. 代码行模式规则: 
    a. 当"//"位于代码行尾时,应与代码间至少有1个空格: "code; // comments"
  
  Note: 在规范检查过程中, 会将这些注释替换为空, 不影响行数, 也不修改原文件.

  Errors: 单行模式: '//'和注释间, 没有空格, 或有多个空格.
    category: [whitespace/sigle_line_comment]
    confidence: [5]
    message: [(Newline mode) Format error, syntax: "// xxx".]

  Errors: 单行模式: 注释不应该在文件的最后一行.
    category: [whitespace/sigle_line_comment]
    confidence: [5]
    message: [(Newline mode) Should not be the last line.]

  Errors: 单行模式: 注释的下一行不应该为空.
    category: [whitespace/sigle_line_comment]
    confidence: [5]
    message: [(Newline mode) Should not be blank in next line.]

  Errors: 单行模式: 注释的缩进, 应和下一行相等.
    category: [whitespace/sigle_line_comment]
    confidence: [5]
    message: [(Newline mode) Indentation error, should be same with next line.]

  Errors: 多行模式: '//'前和后, 有且只有一个空格.
    category: [whitespace/sigle_line_comment]
    confidence: [5]
    message: [(Code-line mode) format error. syntax: "code // comments".]

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """

  _SINGLE_LINE_COMMENTS_NEWLINE_MODE_PATTERN = re.compile(r'^\s*///*')
  _SINGLE_LINE_COMMENTS_NEWLINE_MODE_RULE_PATTERN = re.compile(r'^\s*// \S.*$')
  _SINGLE_LINE_COMMENT_CODE_LINE_MODE_PATTERN = re.compile(r'^\s*[^ \t\r\n\f/].*///*')
  _SINGLE_LINE_COMMENT_CODE_LINE_MODE_RULE_PATTERN = re.compile(r'\S // \S.*$')

  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _SINGLE_LINE_COMMENTS_NEWLINE_MODE_PATTERN.match(lines[line_num]): # 检查是否单行注释的新行模式
      if not _SINGLE_LINE_COMMENTS_NEWLINE_MODE_RULE_PATTERN.match(lines[line_num]): # 检查注释规范是否正确.
        _Error(filename, line_num, 'whitespace/sigle_line_comment', 5,
          '(Newline mode) Format error, syntax: "// xxx".')
      if line_num == _LINE_LENS_TRUE: # 注释不应该为最后一行
        _Error(filename, line_num, 'whitespace/sigle_line_comment', 5,
          '(Newline mode) Should not be the last line.')
      if not lines[line_num + 1].strip(): # 注释的下一行, 应该有代码
        _Error(filename, line_num, 'whitespace/sigle_line_comment', 5,
          '(Newline mode) Should not be blank in next line.')
      if _CountLeftSpace(lines[line_num]) != _CountLeftSpace(lines[line_num + 1]): # 检查缩进是否与下一行相等
        # 下一行是否为"}", 若是, 则应有4个空格的缩进
        if not (re.match(r'^\s*}.*$', lines[line_num + 1]) and (_CountLeftSpace(lines[line_num]) == _CountLeftSpace(lines[line_num + 1]) + 4)):
          _Error(filename, line_num, 'whitespace/sigle_line_comment', 5,
            '(Newline mode) Indentation error, should be same with next line.')
      # replace this lines as blank
      lines[line_num] = ''

    if _SINGLE_LINE_COMMENT_CODE_LINE_MODE_PATTERN.match(lines[line_num]):
      if not _SINGLE_LINE_COMMENT_CODE_LINE_MODE_RULE_PATTERN.search(lines[line_num]):
        _Error(filename, line_num, 'whitespace/sigle_line_comment', 5,
          '(Code-line mode) format error. syntax: "code // comments"')
      # replace this comments as blank
      # print(line_num, lines[line_num])
      lines[line_num] = re.sub(r'//.*$', '', lines[line_num])
      # print(line_num, lines[line_num])


def GuardCheckForMultiLineCommentAndReplaceByBlank(filename, file_extention, lines):
  """检查多行注释规范"/* */":
  多行注释, 分为 '单行模式', '代码单行模式', '多行模式'.
  1. 单行模式: 整个注释位于新行, 前后无代码. 
  2. 代码单行模式: 注释位于代码行行尾或中间. 
  3. 多行模式: 整个注释包含2行或以上. 
  example:
  /**
   * comments
   * comments
   *
   */

  1. 单行模式规则: 
    a. 注释内容, 与注释符号之间, 应有且仅有一个空格:/* xxx */
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Single-line mode) Format error syntax: "   /* xxx */".]
    b. 注释不应为最后一行. 
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Single-line mode) Should not be the last line.']
    c. 当下一行不为空时, 缩进因与下一行一致. (当下一行有'}'符号时, 缩进应比下一行多4个空格)
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Single-line mode) Indentation error, should be same with next line.]
      TODO: 当下一行为空时, 检查缩进应为顶行书写. 
  2. 单行代码模式规则: 
    a. 注释内容与注释符之间, 有且只有一个空格: "/* comments */"
    b. 注释应位于行末, 不应夹在代码之间: "code /* comments */ code;"
    c. 注释内容与代码之间, 有且只有一个空格: "code; /* comments */"
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Code-line mode) Syntax error: "code /* comments */"]
  3. 多行模式规则: 
    a. 起始行应使用新行, 前后不能有代码, 起始行使用符号"/**". 
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Multi-line mode) Start-line syntax error: "newline /**$".]
    b. 起始行的缩进, 应比下一行少一个空格: " *" 比 "/**"多一个空格. 
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Multi-line mode) Start-line indentation error.]
    c. 中间行使用符号" * comments" 或 " *", 注意'*'前的空格. 
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Multi-line mode) Mid-line syntax error: " * comments$".]
    d. 中间行缩进, 应和下一行一致. 
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Multi-line mode) Mid-line indentation error.]
    e. 结束行应使用新行, 前后不能有代码, 使用符号" */",  注意空格. 
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Multi-line mode) Stop-line syntax error: " */$'.]
    f. 结束行, 当下一行不为空时, 缩进应比下一行多一个空格: " */"前面有一个空格. 
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Multi-line mode) Stop-line indentation error. Next line is not None.]
    g. 结束行, 当下一行为空时, 缩进应为1个空格: " */"前面有一个空格. 
      Errors:
      category: [whitespace/multi_line_comment]
      confidence: [5]
      message: [(Multi-line mode) Stop-line indentation error. Next line is None.]

  Note: 在规范检查过程中, 会将这些注释替换为空, 不影响行数, 也不修改原文件.

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  _MULTI_LINE_COMMENT_SINGLE_LINE_MODE_PATTERN = re.compile(r'^\s*/\*.*\*/\s*$') # "newline /* comments */"
  _MULTI_LINE_COMMENT_SINGLE_LINE_MODE_RULE_PATTERN = re.compile(r'^\s*/\* \S+ \*/$')
  _MULTI_LINE_COMMENT_CODE_LINE_MODE_PATTERN = re.compile(r'\S.*/\*.*\*/') # "code /* comments */"
  _MULTI_LINE_COMMENT_CODE_LINE_MODE_RULE_PATTERN = re.compile(r'\S /\* \S.*\S \*/$')
  _MULTI_LINE_COMMENT_MULIT_LINE_MODE_START_PATTERN = re.compile(r'/\*+') # "/**"
  _MULTI_LINE_COMMENT_MULIT_LINE_MODE_START_RULE_PATTERN = re.compile(r'^\s*/\**$')
  _MULTI_LINE_COMMENT_MULIT_LINE_MODE_MID_RULE_PATTERN = re.compile(r'^\s* \*(.*\S)?$')
  _MULTI_LINE_COMMENT_MULIT_LINE_MODE_STOP_PATTERN = re.compile(r'\*/') # " */"
  _MULTI_LINE_COMMENT_MULIT_LINE_MODE_STOP_RULE_PATTERN = re.compile(r'^\s* \*/$')

  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _MULTI_LINE_COMMENT_SINGLE_LINE_MODE_PATTERN.match(lines[line_num]): # 是否为单行模式
      if not _MULTI_LINE_COMMENT_SINGLE_LINE_MODE_RULE_PATTERN.match(lines[line_num]): # 单行模式是否规范
        # print(line_num, lines[line_num])
        _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
          '(Single-line mode) Format error syntax: "   /* xxx */".')
      if line_num == _LINE_LENS_TRUE: # 注释不应该为最后一行
        _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
          '(Single-line mode) Should not be the last line.')
      # if not lines[line_num + 1].strip(): # 注释的下一行, 应该有代码 # 对于需要start 和 end的, 此处无法检查, 取消.
      #   _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
      #     '(Single-line mode) Should not be blank in next line.')
      if (_CountLeftSpace(lines[line_num]) != _CountLeftSpace(lines[line_num + 1])) and (not lines[line_num + 1].strip()): # 检查缩进是否与下一行相等 # 当下一行部位空时, 才检查缩进
        # 下一行是否为"}", 若是, 则应有4个空格的缩进
        if not (re.match(r'^\s*}.*$', lines[line_num + 1]) and (_CountLeftSpace(lines[line_num]) == _CountLeftSpace(lines[line_num + 1]) + 4)):
          _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
            '(Single-line mode) Indentation error, should be same with next line.')
      lines[line_num] = ''

  # 把单行模式全部替代空格后, 重新开始匹配, 避免注释影响.
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _MULTI_LINE_COMMENT_CODE_LINE_MODE_PATTERN.search(lines[line_num]): # 代码行的单行模式,  缩进随代码行本身, 不检查此行缩进
      if not _MULTI_LINE_COMMENT_CODE_LINE_MODE_RULE_PATTERN.search(lines[line_num]): # 注释是否规范
        _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
          '(Code-line mode) Syntax error: "code /* comments */"')
      lines[line_num] = re.sub(r'/\*.*?\*/', '', lines[line_num])
  
  start_line = None # 记录是否已找到一个多行注释的起始行.
  # 把单行模式、代码行模式, 全部代空后, 只剩下多行模式的注释, 包括新行的多行模式、和跨代码行的多行模式.
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP): 
    if _MULTI_LINE_COMMENT_MULIT_LINE_MODE_START_PATTERN.search(lines[line_num]): # 检查是否为多行模式的起始行
      if not _MULTI_LINE_COMMENT_MULIT_LINE_MODE_START_RULE_PATTERN.match(lines[line_num]): # 检查规范
        _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
          '(Multi-line mode) Start-line syntax error: "newline /**$".')
      start_line = line_num # 标记已找到注释起始行, 后面开始匹配中间行和结束行

      # 检查多行模式开始行的缩进, 从上到下, 本行与下一行比较
      if _CountLeftSpace(lines[line_num]) + 1 != _CountLeftSpace(lines[line_num + 1]):
        _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
          '(Multi-line mode) Start-line indentation error.')

       # 将注释替换成空白, 注意"/*"前面可能有代码.
      lines[line_num] = re.sub(r'/\*.*$', '', lines[line_num])

      continue # 寻找下一行, 不能在开始行匹配中间行和结束行规则

    # 已经找到了注释开始行, 开始匹配中间行和结束行
    if start_line:
      if not _MULTI_LINE_COMMENT_MULIT_LINE_MODE_STOP_PATTERN.search(lines[line_num]): # 中间行(不是结束行)
        if not _MULTI_LINE_COMMENT_MULIT_LINE_MODE_MID_RULE_PATTERN.match(lines[line_num]): # 检查中间行规则
          _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
          '(Multi-line mode) Mid-line syntax error: " * comments$".')
  
        # 检查多行模式的中间行缩进, 从上到下, 本行与下一行比较
          if _CountLeftSpace(lines[line_num]) != _CountLeftSpace(lines[line_num + 1]):
            _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
              '(Multi-line mode) Mid-line indentation error.')

        lines[line_num] = '' # 直接将中间行替换为空白
      else: # 结束行
        if not _MULTI_LINE_COMMENT_MULIT_LINE_MODE_STOP_RULE_PATTERN.match(lines[line_num]): # 检查结束行规则
          _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
          '(Multi-line mode) Stop-line syntax error: " */$.')

        """
        检查多行模式的结束行缩进, 从上到下, 本行与下一行比较
        若下一行不为空(有代码), 则此多行注释应匹配代码行缩进
  
        当下一行为空, 则此多行注释应该为顶行注释(所以如果此多行注释在{}, 则注释末行的下一行, 一定不能为空)
          当下一行为空时.由于之前已经将"//"和单行模式的"/**/"都替换为空了, 所以此处下一行也有可能是被置换为空的那行(原先为单行注释).
          所以多行模式, 紧接下一行, 如果为单行注释, 这种情况是不允许的.
        """
        if not lines[line_num + 1].strip(): # 下一行不为空
          if _CountLeftSpace(lines[line_num]) != _CountLeftSpace(lines[line_num + 1]) + 1:
            _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
              '(Multi-line mode) Stop-line indentation error. Next line is not None.')
        else:
          if _CountLeftSpace(lines[line_num]) != 1:
            _Error(filename, line_num, 'whitespace/multi_line_comment', 5,
              '(Multi-line mode) Stop-line indentation error. Next line is None.')
            
        start_line = None # 注释已经结束, 重置起始标志, 便于下一个注释的查找.

        # 将注释替换为空白, 注意'*/'后面可能还有代码, 使用非贪婪模式
        lines[line_num] = re.sub(r'.*?\*/', '', lines[line_num])

      # print(line_num, lines[line_num])
  # print(lines)


"""运算符优先级
  优先级    运算符    名称或含义          使用形式                    结合方向      说明
  -----     ----     ---------          -------                   ---------     -----
    1        []       数组下标          数组名[常量表达式]            左到右
    1        ()       圆括号            (表达式)/函数名(形参表)       左到右
    1        .        成员选择(指针)    对象.成员名                   左到右
    1        ->       成员选择(指针)    对象指针->成员名              左到右

    2        -        负号              -表达式                      右到左       单目
    2        (类型)   强制类型转换      (数据类型)表达式              右到左        单目
    2        ++       自增             ++变量名/变量名++             右到左        单目
    2        --       自减             --变量名/变量名--             右到左        单目
    2        *        取值             *指针变量                    右到左         单目
    2        &        取地址           &变量名                       右到左        单目
    2        !        逻辑非           !表达式                       右到左        单目
    2        ~        按位取反         ~表达式                       右到左        单目
    2        sizeof   长度运算         sizeof(表达式)                右到左        单目

    3        /        除               表达式 / 表达式               左到右        双目
    3        *        乘               表达式 * 表达式               左到右        双目
    3        %        取余             整形表达式 % 整形表达式        左到右        双目

    4        +        加               表达式 + 表达式               左到右        双目
    4        -        减               表达式 - 表达式               左到右        双目

    5        <<       左移             变量 << 表达式                左到右        双目
    5        >>       右移             变量 >> 表达式                左到右        双目

    6        >        大于             表达式 > 表达式               左到右        双目
    6        >=       大于等于         表达式 >= 表达式               左到右        双目
    6        <        小于             表达式 < 表达式               左到右        双目
    6        <=       小于等于         表达式 <= 表达式              左到右        双目

    7        ==       等于             表达式 == 表达式              左到右        双目
    7        !=       不等于           表达式 != 表达式              左到右        双目

    8        &        按位与           表达式 & 表达式               左到右        双目

    9        ^        按位异或         表达式 ^ 表达式               左到右        双目

    10       |        按位或           表达式 | 表达式               左到右        双目

    11       &&       逻辑与           表达式 && 表达式               左到右        双目

    12       ||       逻辑或           表达式 || 表达式               左到右        双目

    13       ?:       条件运算符        表达式? 表达式 : 表达式       右到左         三目

    14       =        赋值             变量 = 表达式                  右到左        双目
    14       /=       除后赋值          变量 /= 表达式                右到左        双目
    14       *=       乘后赋值          变量 *= 表达式                右到左        双目
    14       %=       取模后赋值        变量 %= 表达式                右到左        双目
    14       +=       加后赋值          变量 += 表达式                右到左        双目
    14       -=       减后赋值          变量 -= 表达式                右到左        双目
    14       <<=      左移后赋值        变量 <<= 表达式               右到左        双目
    14       >>=      右移后赋值        变量 >>= 表达式               右到左        双目
    14       &=       按位与后赋值      变量 &= 表达式                右到左        双目
    14       ^=       按位异或后赋值    变量 ^= 表达式                右到左        双目
    14       |=       按位或后赋值      变量 |= 表达式                右到左        双目

    15       ,        逗号运算符        表达式,  表达式                左到右        从左向右顺序运算
"""
def _GuardCheckForBiaryOperaterSyntax(filename, file_extention, lines):
  """TODO: 检查双目运算符格式 (注释已删除, 已排除注释中逗号的影响)
  1. 排除字符串影响
  2. 单符号和多符号组合
  3. '+', '-', '/', '%', '<<', '>>', '<', '>', '<=', '>=', '==', '!=', '|', ';', '&&', '||', 
    '=', '*=', '/=', '%=', '+=', '-=', '<<=', '>>=', '&=', '^=', '|=', '<:', ':>', '<%', '%>', '%:', '%:%:'
  4. '->', '.'
  5. 三目运算符'? :'

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForUnaryOperatorSyntax(filename, file_extention, lines):
  """TODO: 检查单目运算符格式(注释已删除, 已排除注释中逗号的影响)
  1. 排除字符串影响
  2. '++', '--', '~', '!', '^', 
  3. '&' (取址, 位与)
  4. '*' (乘, 指针)
  5. '...'
  6.  ';', ','

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForCommaSyntax(filename, file_extention, lines):
  """TODO: 检查逗号规范, 应符合'\\S, ' (注释已删除, 已排除注释中逗号的影响)
  1. 排除enum影响
  2. 排除字符串影响

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  _COMMA_PATTERN = re.compile(r',')
  _COMMA_RULE_PATTERN = re.compile(r'\S, ')
  for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
    if _COMMA_PATTERN.search(lines[line_num]) and _COMMA_RULE_PATTERN.search(lines[line_num]):
      pass
    


def _GuardCheckForVariableType(filename, file_extention, lines):
  """TODO: 检查变量类型, 是否在公共文件定义中
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForFunctionComment(filename, file_extention, lines):
  """TODO: 检查函数是否有注释
  1. ".c"文件的函数中应有注释
  2. 注释应符合规范
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForIndent(filename, file_extention, lines):
  """TODO: 检查缩进是否规范
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForFunctionDefine(filename, file_extention, lines):
  """TODO: 检查函数定义是否规范
  1. 中括号
  2. 圆括号规范
  2. 空格规范
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForVariableDefine(filename, file_extention, lines):
  """TODO: 检查变量命名是否规范
  1. 中括号
  2. 全局变量
  3. 局部变量
  4. 指针
  5. 空格
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForStructDefine(filename, file_extention, lines):
  """TODO: 检查结构体定义是否规范
  1. 中括号位置
  2. 空格

  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForEnumDefine(filename, file_extention, lines):
  """TODO: 检查枚举类型是否规范
  1. 中括号
  2. 全局变量
  3. 局部变量
  4. 指针
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckOperaterFormat(filename, file_extention, lines):
  """TODO: 运算符使用是否规范
  1. 运算符前后空格
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForMalloc(filename, file_extention, lines):
  """TODO: 提醒此处有内存分配,review时注意是否有释放内存
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForArraySize(filename, file_extention, lines):
  """TODO: 数组大小不应超过固定值, 否则应检查
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForFuntionSize(filename, file_extention, lines):
  """TODO: 检查函数是否超长
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _GuardCheckForBrackesStandard(filename, file_extention, lines):
  """TODO: 检查中括号的使用规范
  
  Args:
    filename: 需要处理的文件名.
    file_extention: filename的文件扩展类型, 不含'.'符号, 如'c', 'h'等.
    lines: filename中的所有行列表[].
  """
  pass


def _ProcessFileLines(filename, file_extention, lines):
  GuardCheckForFilenameFormat(filename, file_extention, lines) # 检查文件命名规范
  # GuardCheckForCopyright(filename, file_extention, lines) # 检查是否包含copyright
  GuardCheckForMarcoFormat(filename, file_extention, lines) # 检查 #if等宏是否顶行书写
  GuardCheckForNewlineAtEndOfFile(filename, file_extention, lines) # 检查文件最后一行应为空行
  GuardCheckForLineLength(filename, file_extention, lines) # 检查每一行的长度(包含空格)
  GuardCheckForLineEndingWithBlank(filename, file_extention, lines) # 检查行尾是否有多余空白
  GuardCheckForTabInLine(filename, file_extention, lines) # 检查文件中是否有tab符号
  GuardCheckForLanguage(filename, file_extention, lines) # 文件中不应包含中文字符
  GuardCheckForIncludeFormat(filename, file_extention, lines) # 检查#include格式
  GuardCheckForIncludeInHeaderFile(filename, file_extention, lines) # 头文件中不能有#include
  GuardCheckForHeaderFileHaveMarcoProtect(filename, file_extention, lines) # 头文件应使用#ifdef _FILE_NAME_H_包含
  GuardCheckForSrcFileUseExtern(filename, file_extention, lines) # .c文件中不应使用extern
  GuardCheckForSingleLineCommentAndReplaceByBlank(filename, file_extention, lines) # 检查"//"注释规范 must
  GuardCheckForMultiLineCommentAndReplaceByBlank(filename, file_extention, lines) # 检查"/**/"注释规范 must

  # GuardCheckForCommaSyntax(filename, file_extention, lines) # 检查逗号规范"\S, "
  # fd = open(filename + '_parsed', 'w', encoding='utf8')
  # for line_num in range(_LINE_LENS_RANGE_START, _LINE_LENS_RANGE_STOP):
  #   fd.write(lines[line_num] + '\n')
  # fd.close()
  # GuardFindoutAllMultiLineBLock(filename, file_extention, lines)

  pass


def ProcessFile(filename):
  """Does coding style check on a single file.
  
  Args:
    filename: The name of the file to check.
  """
  file_extention = filename[filename.rfind('.') + 1:]
  if file_extention not in  _valid_extentions:
    print('Unsupported file extention:' + '[' + filename +']')
    return

  try:
    lines = open(filename, mode='r', encoding='utf8').read().split("\n")
  except:
    print("Could not open file:" + filename)
    return

  if _CheckFileNotEmpty is False:
    print('File is empty, no need check:' + filename)
    return

  lines = (['// marker so line numbers and indices both start at 1'] + lines +
           ['// marker so line numbers end in a known way'])
 
  global _LINE_LENS_RANGE_START
  global _LINE_LENS_RANGE_STOP
  global _LINE_LENS_TRUE
  global _LINE_LENS_WITH_MARKER

  _LINE_LENS_RANGE_START = 1
  _LINE_LENS_RANGE_STOP = len(lines) -1
  _LINE_LENS_TRUE = len(lines) - 2
  _LINE_LENS_WITH_MARKER = len(lines)

  _ProcessFileLines(filename, file_extention, lines)


def main():
  filenames = sys.argv[1:]
  if _test_files:
    filenames = _test_files

  if not filenames:
    _PrintUsage('No file specified')

  _clint_state.ResetErrorCounts()

  for filename in filenames:
      ProcessFile(filename)

  # Print the error counts
  sys.exit(_clint_state.error_count > 0)

if __name__ == '__main__':
    main()
